package arkham.data.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import arkham.data.ModEffect;
import arkham.data.Monster;
import arkham.data.Spell;
import arkham.data.UsableEffect;
import arkham.jaxb.encounter.EffectType;
import arkham.jaxb.encounter.EventNode;
import arkham.jaxb.encounter.RollType;
import arkham.util.AHLogger;
import arkham.util.Pair;

/**
 * This class keeps track of the state of an event
 * @author ctheng
 *
 */
public class EventState
{
	public static enum EventStateType
	{
		NEW_EVENT,
		EVENT_ROLL,
		EVENT_CHOICE,
		EFFECT_DECISION,
		SPELL_ACTIVATION,
		USABLE_SPELL,
		MODIFIER_USAGE,
		MOVE_MONSTER,
		ENCOUNTER_MONSTER,
		MONSTER_EVADE,
		MONSTER_HORROR,
		MONSTER_COMBAT,
		ROLL_BEFORE,
		ROLL_DURING,
		ROLL_AFTER;
	}

	private EventNode node;
	private EffectType effect;
	private Spell spell;
	private UsableEffect ueff;
	private List<ModEffect> modEffectList;
	private Monster monster;
	private RollType roll;
	private List<Integer> dice;
	private EventStateType type;
	private HashMap<String, Object> properties;
	private EventState prevState;
	private boolean modEffectsSelected;
	private int player;
	private String message;

	private EventState()
	{
		modEffectsSelected = false;
		properties = new HashMap<String, Object>();
		dice = new ArrayList<Integer>();
	}

	/**
	 * Creates a new event state that encompasses an event node, with
	 * no designated player.
	 * @param event The event node
	 * @param type The type of event state
	 */
	public EventState(EventNode event, EventStateType type)
	{
		this(event, type, -1);
	}

	/**
	 * Creates a new event state that encompasses an event node
	 * @param event The event node
	 * @param type The type of event state
	 * @param player the player this event applies to
	 */
	public EventState(EventNode event, EventStateType type, int player)
	{
		this();
		this.node = event;
		this.type = type;
		this.player = player;
	}

	public EventState(EffectType effect, EventStateType type)
	{
		this(effect, type, -1);
	}

	public EventState(EffectType effect, EventStateType type, int player)
	{
		this();
		this.effect = effect;
		this.type = type;
		this.player = player;
	}

	/**
	 * Creates a new event state for combat
	 * @param mon the monster to fight
	 * @param type the event state type
	 * @param player the player that this event applies to
	 */
	public EventState(Monster mon, EventStateType type, int player)
	{
		this();
		this.monster = mon;
		this.type = type;
		this.player = player;
	}

	public EventState(List<ModEffect> modList, EventStateType type)
	{
		this(modList, type, -1);
	}

	public EventState(List<ModEffect> modList, EventStateType type, int player)
	{
		this();
		this.modEffectList = modList;
		this.type = type;
		this.player = player;
	}

	public EventState(RollType roll, EventStateType type)
	{
		this(roll, type, -1);
	}

	public EventState(RollType roll, EventStateType type, int player)
	{
		this();
		this.roll = roll;
		this.type = type;
		this.player = player;
	}

	public EventState(Spell spell, EventStateType type, int player)
	{
	    this();
	    this.setSpell(spell);
	    this.type = type;
	    this.player = player;
	}

	public EventState(UsableEffect ueff, EventStateType type, int player)
	{
	    this();
	    this.setUsableEffect(ueff);
	    this.type = type;
	    this.player = player;
	}

	/**
	 * @return the type
	 */
	public EventStateType getType() {
		return type;
	}

	/**
	 * @param type the type to set
	 */
	public void setType(EventStateType type) {
		this.type = type;
	}

	/**
	 * @return the effect
	 */
	public EffectType getEffect() {
		return effect;
	}

	/**
	 * @return the node
	 */
	public EventNode getNode() {
		return node;
	}

	/**
	 * @return the monster
	 */
	public Monster getMonster() {
		return monster;
	}

	/**
	 * @return the list of modEffects
	 */
	public List<ModEffect> getModEffects() {
		if (modEffectList == null)
		{
			modEffectList = new ArrayList<ModEffect>();
		}
		return modEffectList;
	}

	/**
	 * @return the roll
	 */
	public RollType getRoll() {
		return roll;
	}

	/**
	 * Sets the roll type
	 * @param roll the roll details to set
	 */
	public void setRoll(RollType roll)
	{
		this.roll = roll;
	}

	/**
	 * @return the dice
	 */
	public List<Integer> getDice() {
		return dice;
	}

	/**
	 * @param player the player to set
	 */
	public void setPlayer(int player) {
		this.player = player;
	}

	/**
	 * @return the player
	 */
	public int getPlayer() {
		return player;
	}

	/**
     * @param spell the spell to set
     */
    public void setSpell(Spell spell)
    {
        this.spell = spell;
    }

    /**
     * @return the spell
     */
    public Spell getSpell()
    {
        return spell;
    }

    /**
     * @param ueff the ueff to set
     */
    public void setUsableEffect(UsableEffect ueff)
    {
        this.ueff = ueff;
    }

    /**
     * @return the ueff
     */
    public UsableEffect getUsableEffect()
    {
        return ueff;
    }

    /**
	 * Gets a property by name
	 * @param propName the name of the property
	 * @return the property if it exists, null otherwise
	 */
	public Object getProperty(String propName)
	{
		return properties.get(propName);
	}

	/**
	 * Adds a property to the event state
	 * @param name the name of the property
	 * @param property the property
	 */
	public void addProperty(String name, Object property)
	{
		properties.put(name, property);
	}

	/**
	 * Checks if a property is defined
	 * @param propName the property name
	 * @return true if the property is defined
	 */
	public boolean isDefined(String propName)
	{
		return properties.containsKey(propName);
	}

	/**
	 * Clears a property from this event state
	 * @param propName the property name
	 */
	public void clearProperty(String propName)
	{
		properties.remove(propName);
	}

	/**
	 * Modifies an integer property by a certain amount. This method
	 * does nothing if the existing property is not an integer
	 * @param propName the property name
	 * @param amount the amount to modify by
	 */
	public void modifyIntegerProperty(String propName, int amount)
	{
	    if (isDefined(propName))
	    {
	        Object prop = properties.get(propName);
	        if (prop instanceof Integer)
	        {
	            properties.put(propName, (Integer)prop + amount);
	        }
	        else
	        {
	            AHLogger.warning("EventState", propName + " is not an integer.");
	        }
	    }
	    else
	    {
	        properties.put(propName, new Integer(amount));
	    }
	}

	/**
	 * @param message the message to set
	 */
	public void setMessage(String message) {
		this.message = message;
	}

	/**
	 * @return the message
	 */
	public String getMessage() {
		return message;
	}

	/**
	 * Sets a flag to state that all mod effects that the player wishes
	 * to apply has been selected. Proceed straight to effect resolution.
	 * @param modEffectsSelected the modEffectsSelected to set
	 */
	public void setModEffectsSelected(boolean modEffectsSelected) {
		this.modEffectsSelected = modEffectsSelected;
	}

	/**
	 * @return the modEffectsSelected
	 */
	public boolean isModEffectsSelected() {
		return modEffectsSelected;
	}

	/**
	 * Sets the previous event state
	 * @param prevState the previous event state to set
	 */
	public void setPrevState(EventState prevState)
	{
		this.prevState = prevState;
	}

	/**
	 * Gets the previous event state
	 * @return the previous event state
	 */
	public EventState getPrevState()
	{
		return prevState;
	}

	/**
	 * Inherits the properties of another state. Will override existing
	 * properties
	 * @param state the state to inherit from
	 */
	public void inheritProperties(EventState state)
	{
	    for (Map.Entry<String, Object> entry : state.properties.entrySet())
	    {
	        addProperty(entry.getKey(), entry.getValue());
	    }
	}

	/**
	 * Inherits a particular property of another state. Will override existing
	 * properties
	 * @param state the state to inherit from
	 * @param property the property to inherit
	 */
	public void inheritProperty(EventState state, String property)
	{
	    if (state.isDefined(property))
	    {
	        addProperty(property, state.getProperty(property));
	    }
	}
}
